﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

namespace MyPublisher.Composition.XWord
{
    public class OpenXmlComposition : IComposition
    {
        public OpenXmlComposition() { }

        public void Combine(string[] sourceDocuments, string resultDocument)
        {
            this.Combine(sourceDocuments, resultDocument, null);
        }

        public void Combine(string[] sourceDocuments, string resultDocument, string templateDocument)
        {
            string newResultDocument = CombineInternal(sourceDocuments, templateDocument, resultDocument);
            if (newResultDocument != resultDocument)
            {
                try
                {
                    if (File.Exists(newResultDocument))
                        File.Delete(newResultDocument);
                }
                catch (System.Exception) { }
            }
        }

        public string CombineToText(string[] sourceDocuments)
        {
            return this.CombineToText(sourceDocuments, null);
        }

        public string CombineToText(string[] sourceDocuments, string templateDocument)
        {
            string resultDocument = CombineInternal(sourceDocuments, templateDocument,null);
            try
            {
                string text = "";
                using (StreamReader reader = new StreamReader(resultDocument))
                {
                    text = reader.ReadToEnd();
                }

                if (File.Exists(resultDocument))
                    File.Delete(resultDocument);

                return text;
            }
            catch (System.Exception ex)
            {
                if (File.Exists(resultDocument))
                    File.Delete(resultDocument);

                throw new ApplicationException("Failed to save document to result path.", ex);
            }
        }

        public string CombineInternal(string[] sourceDocuments, string templateDocument, string resultDocument)
        {
            return CombineDocuments(sourceDocuments, templateDocument, resultDocument);
        }

        #region IComposition Members

        public void Composite(Fragments inputs, Fragment output)
        {
            List<string> sourceDocuments = new List<string>();
            inputs.ForEach((frag) => { sourceDocuments.Add(frag.Path); });

            string resultDocument = output.Path;
            this.Combine(sourceDocuments.ToArray(),resultDocument);
        }

        #endregion

        public static string CombineDocuments(string[] sourceDocuments, string templateDocument, string resultDocument)
        {
            if (sourceDocuments.Length == 0)
                return null;

            ValidateSourceDocuments(sourceDocuments);

            bool needDeleteTemplatedocument = false;
            bool needDeleteResultDocument = false;
            try
            {
                string oriTemplateDocument = templateDocument;
                string oriResultDocument = templateDocument;
                CreateAndFillTemplateFile(sourceDocuments, ref templateDocument);
                CreateResultFile(templateDocument, ref resultDocument);

                needDeleteTemplatedocument = (oriTemplateDocument != templateDocument);
                if (needDeleteTemplatedocument)
                    File.Delete(templateDocument);

                needDeleteResultDocument = (oriResultDocument != resultDocument);
                WordprocessingDocument newDocument = WordprocessingDocument.Open(resultDocument, true);
                using (newDocument)
                {
                    Body targetBody = newDocument.MainDocumentPart.Document.Body;
                    Paragraph placeHolder = new Paragraph();
                    targetBody.AppendChild(placeHolder);

                    newDocument.MainDocumentPart.Document.Save();

                    foreach (string nextSourceDocument in sourceDocuments)
                    {
                        ProcessSingleDocument(new TransformState()
                        {
                            TargetDocument = newDocument,
                            ProcessingDocument = nextSourceDocument
                        });
                    }
                    newDocument.MainDocumentPart.Document.Body.RemoveChild<OpenXmlElement>(placeHolder);
                    newDocument.MainDocumentPart.Document.Save();
                    return resultDocument;
                }
            }
            catch (System.Exception ex)
            {
                if (needDeleteTemplatedocument && File.Exists(templateDocument))
                {
                    DeleteFile(templateDocument);
                }
                if (needDeleteResultDocument && File.Exists(resultDocument))
                {
                    DeleteFile(resultDocument);
                }
                throw new ApplicationException("The transform document failed.", ex);
            }
        }

        private static void ValidateSourceDocuments(string[] sourceDocuments)
        {
            foreach (string nextDocument in sourceDocuments)
            {
                using (WordprocessingDocument document = WordprocessingDocument.Open(nextDocument, false))
                {
                    try
                    {
                        document.Validate(null);
                    }
                    catch (System.Exception ex)
                    {
                        throw new ApplicationException("The document is invalid.", ex);
                    }
                }
            }
        }

        private static void CreateAndFillTemplateFile(string[] sourceDocuments, ref string templateDocument)
        {
            if (String.IsNullOrEmpty(templateDocument))
            {
                templateDocument = Path.GetTempFileName();
                File.Delete(templateDocument);
                templateDocument = templateDocument.Replace(".tmp", ".docx");

                File.Copy(sourceDocuments[0], templateDocument);
            }
            using (WordprocessingDocument document = WordprocessingDocument.Open(templateDocument, true))
            {
                document.MainDocumentPart.Document.Body.RemoveAllChildren<DocumentFormat.OpenXml.Wordprocessing.Paragraph>();
                //document.MainDocumentPart.Document.Body.RemoveAllChildren();
                document.MainDocumentPart.Document.Save();
            }
        }

        private static void CopyStylesContent(string fromDocument, string toDocument)
        {
            using (WordprocessingDocument wordDoc1 = WordprocessingDocument.Open(fromDocument, false))
            using (WordprocessingDocument wordDoc2 = WordprocessingDocument.Open(toDocument, true))
            {
                StyleDefinitionsPart stylePart1 = wordDoc1.MainDocumentPart.StyleDefinitionsPart;
                StyleDefinitionsPart stylePart2 = wordDoc2.MainDocumentPart.StyleDefinitionsPart;

                using (StreamReader streamReader = new StreamReader(stylePart1.GetStream()))
                using (StreamWriter streamWriter = new StreamWriter(stylePart2.GetStream(FileMode.Create)))
                {
                    streamWriter.Write(streamReader.ReadToEnd());
                }
            }
        }

        private static void CreateResultFile(string templateDocument, ref string resultDocument)
        {
            if (String.IsNullOrEmpty(resultDocument))
            {
                resultDocument = Path.GetTempFileName();
                File.Delete(resultDocument);
                resultDocument = resultDocument.Replace(".tmp", ".docx");
            }
            if (File.Exists(resultDocument))
                File.Delete(resultDocument);

            File.Copy(templateDocument, resultDocument);
        }

        private static void ProcessSingleDocument(TransformState transformState)
        {
            WordprocessingDocument targetDocument = transformState.TargetDocument;
            string processingDocument = transformState.ProcessingDocument;

            AlternativeFormatImportPart importPart = targetDocument.MainDocumentPart.AddAlternativeFormatImportPart(AlternativeFormatImportPartType.WordprocessingML);
            using (StreamReader reader = new StreamReader(processingDocument))
            {
               //exclude empty document
                if (reader.BaseStream.Length == 0)
                    return;

               importPart.FeedData(reader.BaseStream);
            }

            Paragraph paragraph = targetDocument.MainDocumentPart.Document.Descendants<Paragraph>().Last();
            AltChunk altChunk = new AltChunk();
            altChunk.AltChunkProperties = new AltChunkProperties();
            altChunk.AltChunkProperties.MatchSource = new MatchSource();
            altChunk.AltChunkProperties.MatchSource.Val = OnOffValue.FromBoolean(false);
            altChunk.Id = targetDocument.MainDocumentPart.GetIdOfPart(importPart);
            paragraph.InsertBeforeSelf(altChunk);
            paragraph.InsertBeforeSelf(new Paragraph(new Run(new Break() { Type = BreakValues.Page })));
        }

        private static void DeleteFile(string fileFullName)
        {
            try
            {
                if (File.Exists(fileFullName))
                {
                    File.Delete(fileFullName);
                }
            }
            catch (Exception e)
            {
            }
        }
    }
}
